﻿using System;

namespace Cavy.UdpClient
{
    public abstract class Transport
    {
        public abstract bool EnableBroadcast { set; get; }
        public abstract int ReceiveBufferSize { get; set; }
        public bool IsReceiving {get; protected set;}

        private string _connectedAddress;
        private int _connectedPort;
        protected internal uint GameId;
        
        public abstract int Bind(int port = 0);

        public int BindLan()
        {
            for (int i = BroadcastLanStartPort; i <= BroadcastLanEndPort; i++)
            {
                try
                {
                    int port = Bind(i);
                    if (port != i)
                    {
                        Close();
                        continue;
                    }
                    UdpDebug.Log("BandLan port: " + port);
                    return port;
                }
                catch (Exception)
                {
                    continue;
                }
            }
            throw new SystemException("BindLan failed");
        }

        internal void _Connect(string address, int port)
        {
            _connectedAddress = address;
            _connectedPort = port;
            Connect(address, port);
        }

        public abstract void Connect(string address, int port);
        public abstract void Close();
        public abstract void Dispose();
        public abstract void ReceiveAsync();
        public abstract void Send(byte[] data, int dataLength, string host, int port);

        public virtual void Broadcast(byte[] data, int dataLength, string host, int port)
        {
            Send(data, dataLength, host, port);
        }

        public void Send(IMessage message, string host, int port)
        {
            Send(GameId, message, host, port);
        }
        
        public void Send(IMessage message, IpEndPoint endPoint)
        {
            Send(GameId, message, endPoint.Address, endPoint.Port);
        }

        public void Send(uint gameId, IMessage message, string host, int port)
        {
            Serializable.NetDataWriter writer = new Serializable.NetDataWriter();
            writer.Put(gameId);
            writer.Put(message.MessageType);
            message.Serialize(writer);
            var data = writer.Data;
            Send(data, data.Length, host, port);
        }
        
        public void Send(uint gameId, IMessage message)
        {
            Serializable.NetDataWriter writer = new Serializable.NetDataWriter();
            writer.Put(gameId);
            writer.Put(message.MessageType);
            message.Serialize(writer);
            var data = writer.Data;
            Send(data, data.Length, _connectedAddress, _connectedPort);
        }
        
        public event System.Action<UdpResult> OnReceived;
        public event System.Action OnConnected;
        public event System.Action OnDisconnected; 
        protected void CallOnReceived(UdpResult result)
        {
            OnReceived?.Invoke(result);
        }

        protected void CallOnConnected()
        {
            OnConnected?.Invoke();
        }

        protected void CallOnDisconnected()
        {
            OnDisconnected?.Invoke();
        }

        public virtual int BroadcastLanStartPort { set; get; } = 7777;
        public virtual int BroadcastLanEndPort { set; get; } = 7788;
        public virtual string BroadcastLanAddress { set; get; } = "255.255.255.255";
        
        public virtual void BroadcastLan(byte[] data, int dataLength)
        {
            for (int i = BroadcastLanStartPort; i <= BroadcastLanEndPort; i++)
            {
                Broadcast(data, dataLength, BroadcastLanAddress, i);
            }
        }

        public virtual void BroadcastLan(IMessage message)
        {
            Serializable.NetDataWriter writer = new Serializable.NetDataWriter();
            writer.Put(GameId);
            writer.Put(message.MessageType);
            message.Serialize(writer);
            var data = writer.Data;
            var dataLength = data.Length;
            for (int i = BroadcastLanStartPort; i <= BroadcastLanEndPort; i++)
            {
                Broadcast(data, dataLength, BroadcastLanAddress, i);
            }
        }

        protected bool IsPlaying
        {
            get
            {
#if UNITY_EDITOR
                return UnityEditor.EditorApplication.isPlaying;
#else
                return true;
#endif
            }
        }
    }

    public struct IpEndPoint
    {
        public string Address;
        public int Port;

        public IpEndPoint(string address, int port)
        {
            Address = address;
            Port = port;
        }
    }

    public struct UdpResult
    {
        public byte[] Buffer;
        public IpEndPoint RemoteEndPoint;
    }
}